home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2008 October / PCgo 2008-10 (DVD).iso / interface / contents / vollversionen_6617 / 21733 / files / xulrunner / components / nsContentPrefService.js < prev    next >
Encoding:
Text File  |  2008-08-20  |  29.3 KB  |  918 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Content Preferences (cpref).
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Myk Melez <myk@mozilla.org>
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. const Ci = Components.interfaces;
  38. const Cc = Components.classes;
  39. const Cr = Components.results;
  40. const Cu = Components.utils;
  41.  
  42. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  43.  
  44. function ContentPrefService() {
  45.   // If this throws an exception, it causes the getService call to fail,
  46.   // but the next time a consumer tries to retrieve the service, we'll try
  47.   // to initialize the database again, which might work if the failure
  48.   // was due to a temporary condition (like being out of disk space).
  49.   this._dbInit();
  50.  
  51.   // Observe shutdown so we can shut down the database connection.
  52.   this._observerSvc.addObserver(this, "xpcom-shutdown", false);
  53. }
  54.  
  55. ContentPrefService.prototype = {
  56.   //**************************************************************************//
  57.   // XPCOM Plumbing
  58.  
  59.   classDescription: "Content Pref Service",
  60.   classID:          Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
  61.   contractID:       "@mozilla.org/content-pref/service;1",
  62.   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentPrefService]),
  63.  
  64.  
  65.   //**************************************************************************//
  66.   // Convenience Getters
  67.  
  68.   // Observer Service
  69.   __observerSvc: null,
  70.   get _observerSvc ContentPrefService_get__observerSvc() {
  71.     if (!this.__observerSvc)
  72.       this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
  73.                            getService(Ci.nsIObserverService);
  74.     return this.__observerSvc;
  75.   },
  76.  
  77.   // Console Service
  78.   __consoleSvc: null,
  79.   get _consoleSvc ContentPrefService_get__consoleSvc() {
  80.     if (!this.__consoleSvc)
  81.       this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
  82.                           getService(Ci.nsIConsoleService);
  83.     return this.__consoleSvc;
  84.   },
  85.  
  86.   // Preferences Service
  87.   __prefSvc: null,
  88.   get _prefSvc ContentPrefService_get__prefSvc() {
  89.     if (!this.__prefSvc)
  90.       this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
  91.                        getService(Ci.nsIPrefBranch2);
  92.     return this.__prefSvc;
  93.   },
  94.  
  95.  
  96.   //**************************************************************************//
  97.   // Destruction
  98.  
  99.   _destroy: function ContentPrefService__destroy() {
  100.     this._observerSvc.removeObserver(this, "xpcom-shutdown");
  101.  
  102.     // Delete references to XPCOM components to make sure we don't leak them
  103.     // (although we haven't observed leakage in tests).  Also delete references
  104.     // in _observers and _genericObservers to avoid cycles with those that
  105.     // refer to us and don't remove themselves from those observer pools.
  106.     for (var i in this) {
  107.       try { this[i] = null }
  108.       // Ignore "setting a property that has only a getter" exceptions.
  109.       catch(ex) {}
  110.     }
  111.   },
  112.  
  113.  
  114.   //**************************************************************************//
  115.   // nsIObserver
  116.  
  117.   observe: function ContentPrefService_observe(subject, topic, data) {
  118.     switch (topic) {
  119.       case "xpcom-shutdown":
  120.         this._destroy();
  121.         break;
  122.     }
  123.   },
  124.  
  125.  
  126.   //**************************************************************************//
  127.   // nsIContentPrefService
  128.  
  129.   getPref: function ContentPrefService_getPref(aURI, aName) {
  130.     if (aURI) {
  131.       var group = this.grouper.group(aURI);
  132.       return this._selectPref(group, aName);
  133.     }
  134.  
  135.     return this._selectGlobalPref(aName);
  136.   },
  137.  
  138.   setPref: function ContentPrefService_setPref(aURI, aName, aValue) {
  139.     // If the pref is already set to the value, there's nothing more to do.
  140.     var currentValue = this.getPref(aURI, aName);
  141.     if (typeof currentValue != "undefined" && currentValue == aValue)
  142.       return;
  143.  
  144.     var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
  145.     var group, groupID, prefID;
  146.     if (aURI) {
  147.       group = this.grouper.group(aURI);
  148.       groupID = this._selectGroupID(group) || this._insertGroup(group);
  149.       prefID = this._selectPrefID(groupID, settingID);
  150.     }
  151.     else {
  152.       group = null;
  153.       groupID = null;
  154.       prefID = this._selectGlobalPrefID(settingID);
  155.     }
  156.  
  157.     // Update the existing record, if any, or create a new one.
  158.     if (prefID)
  159.       this._updatePref(prefID, aValue);
  160.     else
  161.       this._insertPref(groupID, settingID, aValue);
  162.  
  163.     for each (var observer in this._getObservers(aName)) {
  164.       try {
  165.         observer.onContentPrefSet(group, aName, aValue);
  166.       }
  167.       catch(ex) {
  168.         Cu.reportError(ex);
  169.       }
  170.     }
  171.   },
  172.  
  173.   hasPref: function ContentPrefService_hasPref(aURI, aName) {
  174.     // XXX If consumers end up calling this method regularly, then we should
  175.     // optimize this to query the database directly.
  176.     return (typeof this.getPref(aURI, aName) != "undefined");
  177.   },
  178.  
  179.   removePref: function ContentPrefService_removePref(aURI, aName) {
  180.     // If there's no old value, then there's nothing to remove.
  181.     if (!this.hasPref(aURI, aName))
  182.       return;
  183.  
  184.     var settingID = this._selectSettingID(aName);
  185.     var group, groupID, prefID;
  186.     if (aURI) {
  187.       group = this.grouper.group(aURI);
  188.       groupID = this._selectGroupID(group);
  189.       prefID = this._selectPrefID(groupID, settingID);
  190.     }
  191.     else {
  192.       group = null;
  193.       groupID = null;
  194.       prefID = this._selectGlobalPrefID(settingID);
  195.     }
  196.  
  197.     this._deletePref(prefID);
  198.  
  199.     // Get rid of extraneous records that are no longer being used.
  200.     this._deleteSettingIfUnused(settingID);
  201.     if (groupID)
  202.       this._deleteGroupIfUnused(groupID);
  203.  
  204.     for each (var observer in this._getObservers(aName)) {
  205.       try {
  206.         observer.onContentPrefRemoved(group, aName);
  207.       }
  208.       catch(ex) {
  209.         Cu.reportError(ex);
  210.       }
  211.     }
  212.   },
  213.  
  214.   getPrefs: function ContentPrefService_getPrefs(aURI) {
  215.     if (aURI) {
  216.       var group = this.grouper.group(aURI);
  217.       return this._selectPrefs(group);
  218.     }
  219.  
  220.     return this._selectGlobalPrefs();
  221.   },
  222.  
  223.   // A hash of arrays of observers, indexed by setting name.
  224.   _observers: {},
  225.  
  226.   // An array of generic observers, which observe all settings.
  227.   _genericObservers: [],
  228.  
  229.   addObserver: function ContentPrefService_addObserver(aName, aObserver) {
  230.     var observers;
  231.     if (aName) {
  232.       if (!this._observers[aName])
  233.         this._observers[aName] = [];
  234.       observers = this._observers[aName];
  235.     }
  236.     else
  237.       observers = this._genericObservers;
  238.  
  239.     if (observers.indexOf(aObserver) == -1)
  240.       observers.push(aObserver);
  241.   },
  242.  
  243.   removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
  244.     var observers;
  245.     if (aName) {
  246.       if (!this._observers[aName])
  247.         return;
  248.       observers = this._observers[aName];
  249.     }
  250.     else
  251.       observers = this._genericObservers;
  252.  
  253.     if (observers.indexOf(aObserver) != -1)
  254.       observers.splice(observers.indexOf(aObserver), 1);
  255.   },
  256.  
  257.   /**
  258.    * Construct a list of observers to notify about a change to some setting,
  259.    * putting setting-specific observers before before generic ones, so observers
  260.    * that initialize individual settings (like the page style controller)
  261.    * execute before observers that display multiple settings and depend on them
  262.    * being initialized first (like the content prefs sidebar).
  263.    */
  264.   _getObservers: function ContentPrefService__getObservers(aName) {
  265.     var observers = [];
  266.  
  267.     if (aName && this._observers[aName])
  268.       observers = observers.concat(this._observers[aName]);
  269.     observers = observers.concat(this._genericObservers);
  270.  
  271.     return observers;
  272.   },
  273.  
  274.   _grouper: null,
  275.   get grouper ContentPrefService_get_grouper() {
  276.     if (!this._grouper)
  277.       this._grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
  278.                       getService(Ci.nsIContentURIGrouper);
  279.     return this._grouper;
  280.   },
  281.  
  282.   get DBConnection ContentPrefService_get_DBConnection() {
  283.     return this._dbConnection;
  284.   },
  285.  
  286.  
  287.   //**************************************************************************//
  288.   // Data Retrieval & Modification
  289.  
  290.   __stmtSelectPref: null,
  291.   get _stmtSelectPref ContentPrefService_get__stmtSelectPref() {
  292.     if (!this.__stmtSelectPref)
  293.       this.__stmtSelectPref = this._dbCreateStatement(
  294.         "SELECT prefs.value AS value " +
  295.         "FROM prefs " +
  296.         "JOIN groups ON prefs.groupID = groups.id " +
  297.         "JOIN settings ON prefs.settingID = settings.id " +
  298.         "WHERE groups.name = :group " +
  299.         "AND settings.name = :setting"
  300.       );
  301.  
  302.     return this.__stmtSelectPref;
  303.   },
  304.  
  305.   _selectPref: function ContentPrefService__selectPref(aGroup, aSetting) {
  306.     var value;
  307.  
  308.     try {
  309.       this._stmtSelectPref.params.group = aGroup;
  310.       this._stmtSelectPref.params.setting = aSetting;
  311.  
  312.       if (this._stmtSelectPref.step())
  313.         value = this._stmtSelectPref.row["value"];
  314.     }
  315.     finally {
  316.       this._stmtSelectPref.reset();
  317.     }
  318.  
  319.     return value;
  320.   },
  321.  
  322.   __stmtSelectGlobalPref: null,
  323.   get _stmtSelectGlobalPref ContentPrefService_get__stmtSelectGlobalPref() {
  324.     if (!this.__stmtSelectGlobalPref)
  325.       this.__stmtSelectGlobalPref = this._dbCreateStatement(
  326.         "SELECT prefs.value AS value " +
  327.         "FROM prefs " +
  328.         "JOIN settings ON prefs.settingID = settings.id " +
  329.         "WHERE prefs.groupID IS NULL " +
  330.         "AND settings.name = :name"
  331.       );
  332.  
  333.     return this.__stmtSelectGlobalPref;
  334.   },
  335.  
  336.   _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName) {
  337.     var value;
  338.  
  339.     try {
  340.       this._stmtSelectGlobalPref.params.name = aName;
  341.  
  342.       if (this._stmtSelectGlobalPref.step())
  343.         value = this._stmtSelectGlobalPref.row["value"];
  344.     }
  345.     finally {
  346.       this._stmtSelectGlobalPref.reset();
  347.     }
  348.  
  349.     return value;
  350.   },
  351.  
  352.   __stmtSelectGroupID: null,
  353.   get _stmtSelectGroupID ContentPrefService_get__stmtSelectGroupID() {
  354.     if (!this.__stmtSelectGroupID)
  355.       this.__stmtSelectGroupID = this._dbCreateStatement(
  356.         "SELECT groups.id AS id " +
  357.         "FROM groups " +
  358.         "WHERE groups.name = :name "
  359.       );
  360.  
  361.     return this.__stmtSelectGroupID;
  362.   },
  363.  
  364.   _selectGroupID: function ContentPrefService__selectGroupID(aName) {
  365.     var id;
  366.  
  367.     try {
  368.       this._stmtSelectGroupID.params.name = aName;
  369.  
  370.       if (this._stmtSelectGroupID.step())
  371.         id = this._stmtSelectGroupID.row["id"];
  372.     }
  373.     finally {
  374.       this._stmtSelectGroupID.reset();
  375.     }
  376.  
  377.     return id;
  378.   },
  379.  
  380.   __stmtInsertGroup: null,
  381.   get _stmtInsertGroup ContentPrefService_get__stmtInsertGroup() {
  382.     if (!this.__stmtInsertGroup)
  383.       this.__stmtInsertGroup = this._dbCreateStatement(
  384.         "INSERT INTO groups (name) VALUES (:name)"
  385.       );
  386.  
  387.     return this.__stmtInsertGroup;
  388.   },
  389.  
  390.   _insertGroup: function ContentPrefService__insertGroup(aName) {
  391.     this._stmtInsertGroup.params.name = aName;
  392.     this._stmtInsertGroup.execute();
  393.     return this._dbConnection.lastInsertRowID;
  394.   },
  395.  
  396.   __stmtSelectSettingID: null,
  397.   get _stmtSelectSettingID ContentPrefService_get__stmtSelectSettingID() {
  398.     if (!this.__stmtSelectSettingID)
  399.       this.__stmtSelectSettingID = this._dbCreateStatement(
  400.         "SELECT id FROM settings WHERE name = :name"
  401.       );
  402.  
  403.     return this.__stmtSelectSettingID;
  404.   },
  405.  
  406.   _selectSettingID: function ContentPrefService__selectSettingID(aName) {
  407.     var id;
  408.  
  409.     try {
  410.       this._stmtSelectSettingID.params.name = aName;
  411.  
  412.       if (this._stmtSelectSettingID.step())
  413.         id = this._stmtSelectSettingID.row["id"];
  414.     }
  415.     finally {
  416.       this._stmtSelectSettingID.reset();
  417.     }
  418.  
  419.     return id;
  420.   },
  421.  
  422.   __stmtInsertSetting: null,
  423.   get _stmtInsertSetting ContentPrefService_get__stmtInsertSetting() {
  424.     if (!this.__stmtInsertSetting)
  425.       this.__stmtInsertSetting = this._dbCreateStatement(
  426.         "INSERT INTO settings (name) VALUES (:name)"
  427.       );
  428.  
  429.     return this.__stmtInsertSetting;
  430.   },
  431.  
  432.   _insertSetting: function ContentPrefService__insertSetting(aName) {
  433.     this._stmtInsertSetting.params.name = aName;
  434.     this._stmtInsertSetting.execute();
  435.     return this._dbConnection.lastInsertRowID;
  436.   },
  437.  
  438.   __stmtSelectPrefID: null,
  439.   get _stmtSelectPrefID ContentPrefService_get__stmtSelectPrefID() {
  440.     if (!this.__stmtSelectPrefID)
  441.       this.__stmtSelectPrefID = this._dbCreateStatement(
  442.         "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
  443.       );
  444.  
  445.     return this.__stmtSelectPrefID;
  446.   },
  447.  
  448.   _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
  449.     var id;
  450.  
  451.     try {
  452.       this._stmtSelectPrefID.params.groupID = aGroupID;
  453.       this._stmtSelectPrefID.params.settingID = aSettingID;
  454.  
  455.       if (this._stmtSelectPrefID.step())
  456.         id = this._stmtSelectPrefID.row["id"];
  457.     }
  458.     finally {
  459.       this._stmtSelectPrefID.reset();
  460.     }
  461.  
  462.     return id;
  463.   },
  464.  
  465.   __stmtSelectGlobalPrefID: null,
  466.   get _stmtSelectGlobalPrefID ContentPrefService_get__stmtSelectGlobalPrefID() {
  467.     if (!this.__stmtSelectGlobalPrefID)
  468.       this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
  469.         "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
  470.       );
  471.  
  472.     return this.__stmtSelectGlobalPrefID;
  473.   },
  474.  
  475.   _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
  476.     var id;
  477.  
  478.     try {
  479.       this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
  480.  
  481.       if (this._stmtSelectGlobalPrefID.step())
  482.         id = this._stmtSelectGlobalPrefID.row["id"];
  483.     }
  484.     finally {
  485.       this._stmtSelectGlobalPrefID.reset();
  486.     }
  487.  
  488.     return id;
  489.   },
  490.  
  491.   __stmtInsertPref: null,
  492.   get _stmtInsertPref ContentPrefService_get__stmtInsertPref() {
  493.     if (!this.__stmtInsertPref)
  494.       this.__stmtInsertPref = this._dbCreateStatement(
  495.         "INSERT INTO prefs (groupID, settingID, value) " +
  496.         "VALUES (:groupID, :settingID, :value)"
  497.       );
  498.  
  499.     return this.__stmtInsertPref;
  500.   },
  501.  
  502.   _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
  503.     this._stmtInsertPref.params.groupID = aGroupID;
  504.     this._stmtInsertPref.params.settingID = aSettingID;
  505.     this._stmtInsertPref.params.value = aValue;
  506.     this._stmtInsertPref.execute();
  507.     return this._dbConnection.lastInsertRowID;
  508.   },
  509.  
  510.   __stmtUpdatePref: null,
  511.   get _stmtUpdatePref ContentPrefService_get__stmtUpdatePref() {
  512.     if (!this.__stmtUpdatePref)
  513.       this.__stmtUpdatePref = this._dbCreateStatement(
  514.         "UPDATE prefs SET value = :value WHERE id = :id"
  515.       );
  516.  
  517.     return this.__stmtUpdatePref;
  518.   },
  519.  
  520.   _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
  521.     this._stmtUpdatePref.params.id = aPrefID;
  522.     this._stmtUpdatePref.params.value = aValue;
  523.     this._stmtUpdatePref.execute();
  524.   },
  525.  
  526.   __stmtDeletePref: null,
  527.   get _stmtDeletePref ContentPrefService_get__stmtDeletePref() {
  528.     if (!this.__stmtDeletePref)
  529.       this.__stmtDeletePref = this._dbCreateStatement(
  530.         "DELETE FROM prefs WHERE id = :id"
  531.       );
  532.  
  533.     return this.__stmtDeletePref;
  534.   },
  535.  
  536.   _deletePref: function ContentPrefService__deletePref(aPrefID) {
  537.     this._stmtDeletePref.params.id = aPrefID;
  538.     this._stmtDeletePref.execute();
  539.   },
  540.  
  541.   __stmtDeleteSettingIfUnused: null,
  542.   get _stmtDeleteSettingIfUnused ContentPrefService_get__stmtDeleteSettingIfUnused() {
  543.     if (!this.__stmtDeleteSettingIfUnused)
  544.       this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(
  545.         "DELETE FROM settings WHERE id = :id " +
  546.         "AND id NOT IN (SELECT DISTINCT settingID FROM prefs)"
  547.       );
  548.  
  549.     return this.__stmtDeleteSettingIfUnused;
  550.   },
  551.  
  552.   _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
  553.     this._stmtDeleteSettingIfUnused.params.id = aSettingID;
  554.     this._stmtDeleteSettingIfUnused.execute();
  555.   },
  556.  
  557.   __stmtDeleteGroupIfUnused: null,
  558.   get _stmtDeleteGroupIfUnused ContentPrefService_get__stmtDeleteGroupIfUnused() {
  559.     if (!this.__stmtDeleteGroupIfUnused)
  560.       this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(
  561.         "DELETE FROM groups WHERE id = :id " +
  562.         "AND id NOT IN (SELECT DISTINCT groupID FROM prefs)"
  563.       );
  564.  
  565.     return this.__stmtDeleteGroupIfUnused;
  566.   },
  567.  
  568.   _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
  569.     this._stmtDeleteGroupIfUnused.params.id = aGroupID;
  570.     this._stmtDeleteGroupIfUnused.execute();
  571.   },
  572.  
  573.   __stmtSelectPrefs: null,
  574.   get _stmtSelectPrefs ContentPrefService_get__stmtSelectPrefs() {
  575.     if (!this.__stmtSelectPrefs)
  576.       this.__stmtSelectPrefs = this._dbCreateStatement(
  577.         "SELECT settings.name AS name, prefs.value AS value " +
  578.         "FROM prefs " +
  579.         "JOIN groups ON prefs.groupID = groups.id " +
  580.         "JOIN settings ON prefs.settingID = settings.id " +
  581.         "WHERE groups.name = :group "
  582.       );
  583.  
  584.     return this.__stmtSelectPrefs;
  585.   },
  586.  
  587.   _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
  588.     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
  589.                 createInstance(Ci.nsIWritablePropertyBag);
  590.  
  591.     try {
  592.       this._stmtSelectPrefs.params.group = aGroup;
  593.  
  594.       while (this._stmtSelectPrefs.step())
  595.         prefs.setProperty(this._stmtSelectPrefs.row["name"],
  596.                           this._stmtSelectPrefs.row["value"]);
  597.     }
  598.     finally {
  599.       this._stmtSelectPrefs.reset();
  600.     }
  601.  
  602.     return prefs;
  603.   },
  604.  
  605.   __stmtSelectGlobalPrefs: null,
  606.   get _stmtSelectGlobalPrefs ContentPrefService_get__stmtSelectGlobalPrefs() {
  607.     if (!this.__stmtSelectGlobalPrefs)
  608.       this.__stmtSelectGlobalPrefs = this._dbCreateStatement(
  609.         "SELECT settings.name AS name, prefs.value AS value " +
  610.         "FROM prefs " +
  611.         "JOIN settings ON prefs.settingID = settings.id " +
  612.         "WHERE prefs.groupID IS NULL"
  613.       );
  614.  
  615.     return this.__stmtSelectGlobalPrefs;
  616.   },
  617.  
  618.   _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
  619.     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
  620.                 createInstance(Ci.nsIWritablePropertyBag);
  621.  
  622.     try {
  623.       while (this._stmtSelectGlobalPrefs.step())
  624.         prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
  625.                           this._stmtSelectGlobalPrefs.row["value"]);
  626.     }
  627.     finally {
  628.       this._stmtSelectGlobalPrefs.reset();
  629.     }
  630.  
  631.     return prefs;
  632.   },
  633.  
  634.  
  635.   //**************************************************************************//
  636.   // Database Creation & Access
  637.  
  638.   _dbVersion: 3,
  639.  
  640.   _dbSchema: {
  641.     tables: {
  642.       groups:     "id           INTEGER PRIMARY KEY, \
  643.                    name         TEXT NOT NULL",
  644.   
  645.       settings:   "id           INTEGER PRIMARY KEY, \
  646.                    name         TEXT NOT NULL",
  647.   
  648.       prefs:      "id           INTEGER PRIMARY KEY, \
  649.                    groupID      INTEGER REFERENCES groups(id), \
  650.                    settingID    INTEGER NOT NULL REFERENCES settings(id), \
  651.                    value        BLOB"
  652.     },
  653.     indices: {
  654.       groups_idx: {
  655.         table: "groups",
  656.         columns: ["name"]
  657.       },
  658.       settings_idx: {
  659.         table: "settings",
  660.         columns: ["name"]
  661.       },
  662.       prefs_idx: {
  663.         table: "prefs",
  664.         columns: ["groupID", "settingID"]
  665.       }
  666.     }
  667.   },
  668.  
  669.   _dbConnection: null,
  670.  
  671.   _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
  672.     try {
  673.       var statement = this._dbConnection.createStatement(aSQLString);
  674.     }
  675.     catch(ex) {
  676.       Cu.reportError("error creating statement " + aSQLString + ": " +
  677.                      this._dbConnection.lastError + " - " +
  678.                      this._dbConnection.lastErrorString);
  679.       throw ex;
  680.     }
  681.  
  682.     var wrappedStatement = Cc["@mozilla.org/storage/statement-wrapper;1"].
  683.                            createInstance(Ci.mozIStorageStatementWrapper);
  684.     wrappedStatement.initialize(statement);
  685.     return wrappedStatement;
  686.   },
  687.  
  688.   // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
  689.   // specific migration methods) must be careful not to call any method
  690.   // of the service that assumes the database connection has already been
  691.   // initialized, since it won't be initialized until at the end of _dbInit.
  692.  
  693.   _dbInit: function ContentPrefService__dbInit() {
  694.     var dirService = Cc["@mozilla.org/file/directory_service;1"].
  695.                      getService(Ci.nsIProperties);
  696.     var dbFile = dirService.get("ProfD", Ci.nsIFile);
  697.     dbFile.append("content-prefs.sqlite");
  698.  
  699.     var dbService = Cc["@mozilla.org/storage/service;1"].
  700.                     getService(Ci.mozIStorageService);
  701.  
  702.     var dbConnection;
  703.  
  704.     if (!dbFile.exists())
  705.       dbConnection = this._dbCreate(dbService, dbFile);
  706.     else {
  707.       try {
  708.         dbConnection = dbService.openDatabase(dbFile);
  709.       }
  710.       // If the connection isn't ready after we open the database, that means
  711.       // the database has been corrupted, so we back it up and then recreate it.
  712.       catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
  713.         dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
  714.                                                  dbConnection);
  715.       }
  716.  
  717.       // Get the version of the schema in the file.
  718.       var version = dbConnection.schemaVersion;
  719.  
  720.       // Try to migrate the schema in the database to the current schema used by
  721.       // the service.  If migration fails, back up the database and recreate it.
  722.       if (version != this._dbVersion) {
  723.         try {
  724.           this._dbMigrate(dbConnection, version, this._dbVersion);
  725.         }
  726.         catch(ex) {
  727.           Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
  728.           dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
  729.         }
  730.       }
  731.     }
  732.  
  733.     // Turn off disk synchronization checking to reduce disk churn and speed up
  734.     // operations when prefs are changed rapidly (such as when a user repeatedly
  735.     // changes the value of the browser zoom setting for a site).
  736.     //
  737.     // Note: this could cause database corruption if the OS crashes or machine
  738.     // loses power before the data gets written to disk, but this is considered
  739.     // a reasonable risk for the not-so-critical data stored in this database.
  740.     //
  741.     // If you really don't want to take this risk, however, just set the
  742.     // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
  743.     // (FULL synchronization), in which case mozStorageConnection::Initialize
  744.     // will use that value, and we won't override it here.
  745.     if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
  746.       dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
  747.  
  748.     this._dbConnection = dbConnection;
  749.   },
  750.  
  751.   _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
  752.     var dbConnection = aDBService.openDatabase(aDBFile);
  753.  
  754.     try {
  755.       this._dbCreateSchema(dbConnection);
  756.       dbConnection.schemaVersion = this._dbVersion;
  757.     }
  758.     catch(ex) {
  759.       // If we failed to create the database (perhaps because the disk ran out
  760.       // of space), then remove the database file so we don't leave it in some
  761.       // half-created state from which we won't know how to recover.
  762.       dbConnection.close();
  763.       aDBFile.remove(false);
  764.       throw ex;
  765.     }
  766.  
  767.     return dbConnection;
  768.   },
  769.  
  770.   _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
  771.     this._dbCreateTables(aDBConnection);
  772.     this._dbCreateIndices(aDBConnection);
  773.   },
  774.  
  775.   _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
  776.     for (let name in this._dbSchema.tables)
  777.       aDBConnection.createTable(name, this._dbSchema.tables[name]);
  778.   },
  779.  
  780.   _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
  781.     for (let name in this._dbSchema.indices) {
  782.       let index = this._dbSchema.indices[name];
  783.       let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
  784.                       "(" + index.columns.join(", ") + ")";
  785.       aDBConnection.executeSimpleSQL(statement);
  786.     }
  787.   },
  788.  
  789.   _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
  790.                                                                          aDBFile,
  791.                                                                          aDBConnection) {
  792.     aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
  793.  
  794.     // Close the database, ignoring the "already closed" exception, if any.
  795.     // It'll be open if we're here because of a migration failure but closed
  796.     // if we're here because of database corruption.
  797.     try { aDBConnection.close() } catch(ex) {}
  798.  
  799.     aDBFile.remove(false);
  800.  
  801.     let dbConnection = this._dbCreate(aDBService, aDBFile);
  802.  
  803.     return dbConnection;
  804.   },
  805.  
  806.   _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
  807.     if (this["_dbMigrate" + aOldVersion + "To" + aNewVersion]) {
  808.       aDBConnection.beginTransaction();
  809.       try {
  810.         this["_dbMigrate" + aOldVersion + "To" + aNewVersion](aDBConnection);
  811.         aDBConnection.schemaVersion = aNewVersion;
  812.         aDBConnection.commitTransaction();
  813.       }
  814.       catch(ex) {
  815.         aDBConnection.rollbackTransaction();
  816.         throw ex;
  817.       }
  818.     }
  819.     else
  820.       throw("no migrator function from version " + aOldVersion +
  821.             " to version " + aNewVersion);
  822.   },
  823.  
  824.   /**
  825.    * If the schema version is 0, that means it was never set, which means
  826.    * the database was somehow created without the schema being applied, perhaps
  827.    * because the system ran out of disk space (although we check for this
  828.    * in _createDB) or because some other code created the database file without
  829.    * applying the schema.  In any case, recover by simply reapplying the schema.
  830.    */
  831.   _dbMigrate0To3: function ContentPrefService___dbMigrate0To3(aDBConnection) {
  832.     this._dbCreateSchema(aDBConnection);
  833.   },
  834.  
  835.   _dbMigrate1To3: function ContentPrefService___dbMigrate1To3(aDBConnection) {
  836.     aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
  837.     aDBConnection.createTable("groups", this._dbSchema.tables.groups);
  838.     aDBConnection.executeSimpleSQL(
  839.       "INSERT INTO groups (id, name) " +
  840.       "SELECT id, name FROM groupsOld"
  841.     );
  842.  
  843.     aDBConnection.executeSimpleSQL("DROP TABLE groupers");
  844.     aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
  845.  
  846.     this._dbCreateIndices(aDBConnection);
  847.   },
  848.  
  849.   _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
  850.     this._dbCreateIndices(aDBConnection);
  851.   }
  852.  
  853. };
  854.  
  855.  
  856. function HostnameGrouper() {}
  857.  
  858. HostnameGrouper.prototype = {
  859.   //**************************************************************************//
  860.   // XPCOM Plumbing
  861.   
  862.   classDescription: "Hostname Grouper",
  863.   classID:          Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
  864.   contractID:       "@mozilla.org/content-pref/hostname-grouper;1",
  865.   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
  866.  
  867.  
  868.   //**************************************************************************//
  869.   // nsIContentURIGrouper
  870.  
  871.   group: function HostnameGrouper_group(aURI) {
  872.     var group;
  873.  
  874.     try {
  875.       // Accessing the host property of the URI will throw an exception
  876.       // if the URI is of a type that doesn't have a host property.
  877.       // Otherwise, we manually throw an exception if the host is empty,
  878.       // since the effect is the same (we can't derive a group from it).
  879.  
  880.       group = aURI.host;
  881.       if (!group)
  882.         throw("can't derive group from host; no host in URI");
  883.     }
  884.     catch(ex) {
  885.       // If we don't have a host, then use the entire URI (minus the query,
  886.       // reference, and hash, if possible) as the group.  This means that URIs
  887.       // like about:mozilla and about:blank will be considered separate groups,
  888.       // but at least they'll be grouped somehow.
  889.       
  890.       // This also means that each individual file: URL will be considered
  891.       // its own group.  This seems suboptimal, but so does treating the entire
  892.       // file: URL space as a single group (especially if folks start setting
  893.       // group-specific capabilities prefs).
  894.  
  895.       // XXX Is there something better we can do here?
  896.  
  897.       try {
  898.         var url = aURI.QueryInterface(Ci.nsIURL);
  899.         group = aURI.prePath + url.filePath;
  900.       }
  901.       catch(ex) {
  902.         group = aURI.spec;
  903.       }
  904.     }
  905.  
  906.     return group;
  907.   }
  908. };
  909.  
  910.  
  911. //****************************************************************************//
  912. // XPCOM Plumbing
  913.  
  914. var components = [ContentPrefService, HostnameGrouper];
  915. var NSGetModule = function ContentPrefService_NSGetModule(compMgr, fileSpec) {
  916.   return XPCOMUtils.generateModule(components);
  917. }
  918.